define([
    'app',
    'json2',
    'json5',
    'mock',
    'fuzzysearch',
    'zTree-exhide',
    'jquery.format',
    'ace-language-tools'
], function(app, JSON2, JSON5, Mock, fuzzySearch) {
    app.controller('MockEditorController', function($rootScope, $scope, $compile, $http, $location, $timeout,
            $cookies, $routeParams, xDialog, xUtil, toastr) {
        var self = this;

        self.module = $routeParams.module;
        var hasPath = $routeParams.path ? true : false;
        self.fullPath = (hasPath ? '/' + self.module + '/' + $routeParams.path : '');

        self.fullScreen = ($routeParams.fullscreen ? true : false);
        self.fontSize = 14;
        var dataEditor = null;

        self.status = { changed: false, enabled: false };
        self.extname = '';
        self.textMock = '';
        self.canUndo = false;
        self.canRedo = false;
        self.toggleWrap = true;

        var zTreeObj = null;
        var selectedNode = null;
        var mockView = $cookies.getObject('mock_view') || { flatTree: true, formEditor: true };
        self.node = { name: '', root: false, leaf: false, parent: '', url: '', modified: null };

        const keywords = ['标题', '备注', 'URL', 'Method', 'Headers', 'Query', '入参', '出参'];
        const template = '@标题：<%=title%>\n@备注：\n@URL：<%=url%>\n@Method：<%=method%>\n@Headers：\n{}\n'
                + '@Query：\n{}\n@入参：\n<%=params%>\n@出参：';
        const templates = {
            '.json': _.template('/**\n' + template + '\n*/\n'),
            '.xml': _.template('<!--\n' + template + '\n-->\n')
        };

        var initItems = true;
        self.keywords = keywords;
        self.items = {};
        self.itemIdOffset = 0;
        self.preview = $routeParams.preview || mockView.formEditor;
        self.moveZone = { ratio: -1, left: mockView.columnWidth };

        var newCount = 1;
        var setting = {
            view: {
                selectedMulti: false,
                dblClickExpand: false,
                addHoverDom: function(treeId, treeNode) {
                    $('#' + treeNode.tId + '_a').attr('title', (treeNode.oldname || treeNode.name));
                    if (!$rootScope.sessionUser.role || treeNode.editNameFlag) {
                        return;
                    }

                    // Add button
                    if (!treeNode.leaf && $('#addBtn_' + treeNode.tId).length == 0) {
                        var jqBtnAdd = $('<span class="button add" id="addBtn_' + treeNode.tId
                                + '" title="新建" onfocus="this.blur();"></span>');
                        $('#' + treeNode.tId + '_span').after(jqBtnAdd);
                        jqBtnAdd.bind('click', function() {
                            function addTreeNode() {
                                treeNode.parent = getParentPath(treeNode);
                                xDialog.open().editMock({
                                    module: self.module,
                                    treeNode: treeNode,
                                    modifiable: true
                                }).then(function(node) {
                                    setTimeout(function() {
                                        node.name = node.name.replace(/^\//, '');
                                        if (!mockView.flatTree && node.name.indexOf('/') == -1) {
                                            var nodes = $.fn.zTree.getZTreeObj('mock-tree').addNodes(treeNode, {
                                                id: (1e6 + newCount),
                                                pId: treeNode.id,
                                                name: node.name,
                                                leaf: node.leaf,
                                                isParent: !node.leaf
                                            });
                                            zTreeObj.selectNode(nodes[0]);
                                            $('#' + nodes[0].tId + '>a').trigger('click');
                                            countRootApis();
                                        } else {
                                            var path = '/' + node.name + (node.leaf ? '' : '/');
                                            if (node.parent != '/') {
                                                path = node.parent + path;
                                            }
                                            self.fullPath = '/' + self.module + path;
                                            loadTreeNodes();
                                        }
                                    }, 500);
                                });
                            }
                            confirmSaveFile(addTreeNode);

                            return false;
                        });
                    }

                    // Copy button
                    if (!treeNode.root && $('#copyBtn_' + treeNode.tId).length == 0) {
                        var jqBtnCopy = $('<span class="button copy" id="copyBtn_' + treeNode.tId
                                + '" title="复制" onfocus="this.blur();"></span>'),
                            jqBtnRemove = $('#' + treeNode.tId + '_remove');
                        if (jqBtnRemove.length > 0) {
                            jqBtnRemove.before(jqBtnCopy);
                        } else {
                            $('#' + treeNode.tId + '_a').append(jqBtnCopy);
                        }
                        jqBtnCopy.bind('click', function() {
                            function copyMockData() {
                                xDialog.open().copyMock({
                                    type: (treeNode.leaf ? 'file' : 'folder'),
                                    module: self.module,
                                    node: {
                                        parent: getParentPath(treeNode),
                                        name: (treeNode.oldname || treeNode.name),
                                        leaf: treeNode.leaf
                                    }
                                }).then(function(data) {
                                    setTimeout(function() {
                                        self.fullPath = '/' + self.module + data.path;
                                        loadTreeNodes();
                                    }, 500);
                                });
                            }
                            confirmSaveFile(copyMockData);

                            return false;
                        });
                    }

                    // History buttton
                    if ($('#historyBtn_' + treeNode.tId).length == 0) {
                        var jqBtnHistory = $('<span class="button history" id="historyBtn_' + treeNode.tId
                                + '" title="历史" onfocus="this.blur();"></span>'),
                            jqBtnRemove = $('#' + treeNode.tId + '_remove');
                        if (jqBtnRemove.length > 0) {
                            jqBtnRemove.before(jqBtnHistory);
                        } else {
                            $('#' + treeNode.tId + '_a').append(jqBtnHistory);
                        }
                        jqBtnHistory.bind('click', function() {
                            var name = (treeNode.oldname || treeNode.name);
                            var fullPath = xUtil.path.join([self.module, getParentPath(treeNode), name]);
                            if (treeNode.leaf) {
                                xDialog.open().showGitLog({
                                    file: name,
                                    path: fullPath
                                }).then(function(refreshable) {
                                    if (refreshable) {
                                        if (self.node.url == '/' + fullPath) {
                                            $('#' + treeNode.tId + '>a').trigger('click');
                                        }
                                    }
                                });
                            } else {
                                confirmSaveFile(function() {
                                    var url = '/mock-history/' + fullPath + '/';
                                    if (treeNode.root) {
                                        url = '/mock-history/' + self.module + '/';
                                    }
                                    xUtil.location.go(url);
                                });
                            }

                            return false;
                        });
                    }
                },
                removeHoverDom: function(treeId, treeNode) {
                    $('#addBtn_' + treeNode.tId).unbind().remove();
                    $('#copyBtn_' + treeNode.tId).unbind().remove();
                    $('#historyBtn_' + treeNode.tId).unbind().remove();
                }
            },
            data: {
                simpleData: {
                    enable: false
                }
            },
            edit: {
                enable: true,
                removeTitle: '删除',
                showRemoveBtn: function(treeId, treeNode) {
                    return $rootScope.sessionUser.role == 'admin' && !treeNode.root;
                },
                renameTitle: '重命名',
                showRenameBtn: function(treeId, treeNode) {
                    return $rootScope.sessionUser.role && !treeNode.root;
                }
            },
            callback: {
                beforeDrag: function() {
                    return false;
                },
                beforeEditName: function(treeId, treeNode) {
                    var isSelfOrParent = false;
                    if (selectedNode) {
                        var treeNodePath = getNodePath(treeNode),
                            selectedNodePath = getNodePath(selectedNode);
                        if (new RegExp('^' + treeNodePath).test(selectedNodePath)) {
                            isSelfOrParent = true;
                        }
                    }

                    function editNodeName() {
                        treeNode.parent = getParentPath(treeNode);
                        xDialog.open().editMock({
                            module: self.module,
                            treeNode: treeNode,
                            modifiable: false
                        }).then(function(success) {
                            setTimeout(function() {
                                if (success) {
                                    // Update node
                                    zTreeObj.updateNode(treeNode);
                                    // Update file url
                                    if (isSelfOrParent) {
                                        self.node.parent = getParentPath(selectedNode);
                                        self.node.name = (selectedNode.oldname || selectedNode.name);
                                        self.node.url = getNodePath(self.node);
                                        $('#' + selectedNode.tId + '>a').trigger('click');
                                    }
                                }
                            }, 500);
                        });
                    }

                    if (isSelfOrParent) {
                        confirmSaveFile(editNodeName);
                    } else {
                        editNodeName();
                    }

                    return false;
                },
                beforeRemove: function(treeId, treeNode) {
                    var name = (treeNode.oldname || treeNode.name);
                    var path = xUtil.path.join([getParentPath(treeNode), name]);
                    xDialog.confirm('确认信息', '您确定要删除【' + path + '】吗？').then(function(yes) {
                        if (yes) {
                            $http.delete('/mock/data/delete', {
                                params: { module: self.module, path: path }
                            }).then(function(response) {
                                if (response.data == 'required') {
                                    xDialog.alert('提示信息', '数据模块【' + self.module + '】不存在！');
                                } else if (response.data) {
                                    setTimeout(function() {
                                        if (treeNode.leaf) {
                                            toastr.success('文件【' + name + '】已删除！');
                                        } else {
                                            toastr.success('文件夹【' + name + '】已删除！');
                                        }
                                        var parentNode = treeNode.getParentNode();
                                        $.fn.zTree.getZTreeObj('mock-tree').removeNode(treeNode);
                                        // Update folder name after remove it's all children
                                        if (parentNode && parentNode.isParent == false) {
                                            if (!parentNode.leaf) {
                                                parentNode.isParent = true;
                                                zTreeObj.updateNode(parentNode);
                                            }
                                        }
                                        countRootApis();
                                        var nodePath = xUtil.path.join([self.node.parent, self.node.name]);
                                        if ((new RegExp('^' + path)).test(nodePath)) {
                                            self.status.changed = false;
                                            self.node.parent = '';
                                            self.node.name = '';
                                            self.node.url = '';
                                            self.textMock = '';
                                        }
                                    }, 500);
                                }
                            });
                        }
                    });
                    return false;
                },
                beforeClick: function(treeId, treeNode) {
                    if (selectedNode && selectedNode.leaf) {
                        return confirmSaveFile(function() {
                            $('#' + treeNode.tId + '>a').trigger('click');
                        }, false);
                    }
                    return true;
                },
                onClick: function(event, treeId, treeNode) {
                    treeNode.parent = getParentPath(treeNode);
                    selectedNode = treeNode;
                    self.node.parent = treeNode.parent;
                    self.node.name = (treeNode.oldname || treeNode.name);
                    self.node.leaf = treeNode.leaf;
                    self.node.root = treeNode.root;

                    var path = getNodePath(self.node);
                    if (self.node.leaf && /\.md$/i.test(path)) {
                        confirmSaveFile(function() {
                            xUtil.location.go('/markdown-editor/mockdoc' + path);
                        });
                        return false;
                    }
                    self.node.url = path;
                    if (!hasPath) {
                        $location.path('/mock-editor' + path, false).replace();
                    }
                    hasPath = false;

                    if (self.node.leaf) {
                        $http({
                            method: 'POST',
                            url: path + '?origin=true',
                            transformResponse: function(response) {
                                return response;
                            }
                        }).then(function(response) {
                            self.node.modified = response.headers('x-modified');
                            self.status.changed = false;
                            self.status.enabled = true;
                            self.textMock = response.data;
                            // Change editor mode
                            self.extname = path.substring(path.lastIndexOf('.'));
                            var modeId = 'ace/mode/' + (self.extname == '.json' ? 'hjson' : 'xml');
                            if (modeId != dataEditor.getOption('mode')) {
                                dataEditor.getSession().setMode(modeId);
                            }
                            // Set value
                            dataEditor.setValue(self.textMock, -1);
                            dataEditor.resize();
                            dataEditor.focus();
                            if (response.data == '{}' || response.data == '<xml/>') {
                                dataEditor.execCommand('comments');
                            } else {
                                self.gotoTop();
                                checkMockData(response.data);
                            }
                            // undo & redo
                            dataEditor.getSession().getUndoManager().reset();
                            self.canUndo = false;
                            self.canRedo = false;
                            // Preview
                            if (self.preview) {
                                initItems = true;
                                self.itemIdOffset++;
                                self.items = xUtil.mockdoc.parse(dataEditor.getValue());
                                $('.x-form-editor .panel-body').scrollTop(0);
                            }
                        });
                    } else {
                        self.status.changed = false;
                        self.status.enabled = false;
                        self.textMock = '';
                        $timeout(function() { $scope.$apply(); });
                    }
                }
            }
        };

        self.init = function() {
            // Mock.js snippets
            var aceSnippetor = ace.require("ace/ext/language_tools");
            Mock.snippets(aceSnippetor);
            // Editor language tools
            var scoreIdx = 1e5;
            const snippets = _.map([
                { caption: '@Valid', snippet: '@Valid', score: --scoreIdx },
                { caption: '@Null', snippet: '@Null', score: --scoreIdx },
                { caption: '@NotNull', snippet: '@NotNull', score: --scoreIdx },
                { caption: '@NotEmpty', snippet: '@NotEmpty', score: --scoreIdx },
                { caption: '@NotBlank', snippet: '@NotBlank', score: --scoreIdx },
                { caption: '@Length', snippet: '@Length(min = ${1:min}, max = ${2:max})${3}', score: --scoreIdx },
                { caption: '@Min', snippet: '@Min(value = ${1:value})${2}', score: --scoreIdx },
                { caption: '@Max', snippet: '@Max(value = ${1:value})${2}', score: --scoreIdx },
                { caption: '@Size', snippet: '@Size(min = ${1:min}, max = ${2:max})${3}', score: --scoreIdx },
                { caption: '@Range', snippet: '@Range(min = ${1:min}, max = ${2:max})${3}', score: --scoreIdx },
                { caption: '@DecimalMin', snippet: '@DecimalMin(value = "${1:value}")${2}', score: --scoreIdx },
                { caption: '@DecimalMax', snippet: '@DecimalMax(value = "${1:value}")${2}', score: --scoreIdx },
                { caption: '@Past', snippet: '@Past', score: --scoreIdx },
                { caption: '@Future', snippet: '@Future', score: --scoreIdx },
                { caption: '@Pattern', snippet: '@Pattern(regexp = "${1:regexp}", flags = {${2:flags}})${3}', score: --scoreIdx }
            ], function (snippet) {
                snippet['meta'] = 'validator';
                snippet['type'] = 'snippet';
                return snippet;
            });
            aceSnippetor.addCompleter({
                // validation-api @Annotation regexp
                identifierRegexps: [/@[a-zA-Z_0-9]*/],
                getCompletions: function(editor, session, pos, prefix, callback) {
                    if (/^@/.test(prefix)) {
                        return callback(null, snippets);
                    }
                    return callback(null, []);
                }
            });
            // Data editor
            dataEditor = ace.edit('data-editor', {
                mode: 'ace/mode/hjson',
                theme: 'ace/theme/chrome',
                fontSize: self.fontSize,
                wrap: self.toggleWrap,
                showPrintMargin: false,
                autoScrollEditorIntoView: true,
                enableBasicAutocompletion: true,
                enableSnippets: true,
                enableLiveAutocompletion: true
            });
            dataEditor.on('change', function(e) {
                // Apply scope queue
                $timeout(function() {
                    self.status.changed = true;
                    if (dataEditor.getValue() == self.textMock) {
                        self.status.changed = false;
                    }
                    self.canUndo = dataEditor.getSession().getUndoManager().canUndo();
                    self.canRedo = dataEditor.getSession().getUndoManager().canRedo();
                });
            });
            dataEditor.getSession().on('changeAnnotation', function(e) {
                // Check errors
                if (dataEditor.getValue() != self.textMock) {
                    var annotations = dataEditor.getSession().getAnnotations();
                    var canChanged = true;
                    if (!_.isEmpty(annotations)) {
                        if (_.find(annotations, function(annotation) {
                            return annotation.type == 'error';
                        })) {
                            canChanged = false;
                        }
                    }
                    if (self.status.changed != canChanged) {
                        self.status.changed = canChanged;
                        // Apply scope queue
                        $timeout(function() { $scope.$apply(); });
                    }
                }
            });
            dataEditor.on('focus', function() {
                $rootScope.$broadcast('editor.active', dataEditor);
            });
            // Find and replace
            dataEditor.commands.addCommand({
                name: 'replace',
                bindKey: { win: 'Ctrl-F', mac: 'Command-F' },
                exec: function(editor) {
                    self.showSearchbox();
                }
            });
            // Document comment
            dataEditor.commands.addCommand({
                name: 'comments',
                bindKey: { win: 'Ctrl-H', mac: 'Command-H' },
                exec: function(editor) {
                    editor.gotoLine(1);
                    editor.insert(templates[self.extname]({
                        title: '',
                        url: getMockPath(),
                        method: getMockMethod('POST'),
                        params: self.extname == '.json' ? '{}' : '<xml/>'
                    }));
                    editor.moveCursorTo(1, 4);
                }
            });
            // Upgrade document
            dataEditor.commands.addCommand({
                name: 'upgrade',
                bindKey: { win: 'Ctrl-U', mac: 'Command-U' },
                exec: function(editor) {
                    var docValue = editor.getValue();
                    _.forEach(keywords, function(keyword) {
                        var rkeyword = new RegExp('^(' + keyword + ')：', 'gmi');
                        docValue = docValue.replace(rkeyword, function(whole, match) {
                            return whole.replace(match, '@' + keyword);
                        });
                    });
                    var hasQuery = xUtil.regex.hasKey(docValue, 'Query');
                    if (!hasQuery) {
                        var rkeyword = new RegExp('^(@入参)：', 'gmi');
                        docValue = docValue.replace(rkeyword, function(whole, match) {
                            return whole.replace(match, '@Query：\n{}\n@入参');
                        });
                    }
                    editor.setValue(docValue, -1);
                    editor.resize();
                }
            });
            // Refresh URL
            dataEditor.commands.addCommand({
                name: 'refresh',
                bindKey: { win: 'Ctrl-R', mac: 'Command-R' },
                exec: function(editor) {
                    var docValue = editor.getValue()
                    docValue = docValue.replace(/^@URL：(.*)/gmi, function(whole, match) {
                        var params = match.replace(/^\/[^\?]+\??/g, '');
                        return '@URL：' + getMockPath() + (params ? '?' + params : '');
                    }).replace(/^@Method：(.*)/gmi, function(whole, match) {
                        return '@Method：' + getMockMethod(match);
                    });
                    editor.setValue(docValue, -1);
                    editor.resize();
                }
            });
            // Format code
            var formattedCode = '';
            dataEditor.commands.addCommand({
                name: 'format',
                bindKey: { win: 'Ctrl-Shift-F', mac: 'Command-Option-F' },
                exec: function(editor) {
                    if (editor.getValue() != formattedCode) {
                        if (self.extname == '.json') {
                            formattedCode = JSON2.format(editor.getValue(), '    ');
                        } else if (self.extname == '.xml') {
                            formattedCode = $.format(editor.getValue(), { method: 'xml' });
                        } else {
                            formattedCode = editor.getValue();
                        }
                        editor.setValue(formattedCode, -1);
                        editor.resize();
                    }
                }
            });
            // Goto line
            dataEditor.commands.addCommand({
                name: 'gotoline',
                bindKey: { win: 'Ctrl-L', mac: 'Command-L' },
                exec: function(editor) {
                    xDialog.open().showGotoLine({
                        maxLine: editor.session.getLength()
                    }).then(function(line) {
                        editor.gotoLine(line);
                    });
                }
            });
            // Save
            dataEditor.commands.addCommand({
                name: 'save',
                bindKey: { win: 'Ctrl-S', mac: 'Command-S' },
                exec: function(editor) {
                    if (self.status.enabled && self.status.changed) {
                        self.save();
                    }
                }
            });
            // Data editor tools
            $('#data-editor .ace_scroller').append(
                    $compile($('#data-editor-tools').html())($scope)
                );
            $('#data-editor .x-editor-tool').draggable({
                cursor: 'move',
                handle: '.btn-draggable',
                containment: 'parent',
                delay: 200,
                opacity: 0.60,
                drag: function() {
                    $(this).css('right', 'auto');
                }
            }).mousedown(function(e) {
                return false;
            });
            dataEditor.getSession().on('changeScrollTop', function(scrollTop) {
                if (scrollTop > 50) {
                    $('#data-editor .x-editor-tool.x-goto-top').removeClass('hidden');
                } else {
                    $('#data-editor .x-editor-tool.x-goto-top').addClass('hidden');
                }
            });
            // Full screen
            $scope.$watch('vm.fullScreen', function() {
                setTimeout(function() {
                    dataEditor.resize();
                    $('#data-editor .x-editor-tool').css('left', '').css('top', '').css('right', '');
                }, 50);
            });

            // Editor options
            $scope.$watchGroup(["vm.fontSize", "vm.toggleWrap"], function (newValues) {
                $rootScope.$broadcast('editor.options', {
                    fontSize: newValues[0],
                    wrap: newValues[1]
                });
            });
            //
            $scope.$watch('vm.items', function(newValue) {
                if (self.preview && !initItems) {
                    $timeout(function() {
                        dataEditor.setValue(xUtil.mockdoc.stringify(newValue, self.extname), -1);
                    });
                }
                initItems = false;
            }, true);

            // Load tree nodes
            loadTreeNodes();

            // Update column width
            $scope.$on('@zone.resize', function(event, percent) {
                if (!!percent) {
                    mockView.columnWidth = percent;
                    $cookies.putObject('mock_view', mockView);
                }
                dataEditor.resize();
                $scope.$broadcast('@editor.resize');
            });
        }

        self.collapseAll = function() {
            zTreeObj.expandAll(false);
        }

        self.expandAll = function() {
            zTreeObj.expandAll(true);
        }

        self.toggleFlat = function() {
            confirmSaveFile(function() {
                mockView.flatTree = !mockView.flatTree;
                $cookies.putObject('mock_view', mockView);
                self.fullPath = $location.path().replace(/^\/mock-editor/, '');
                loadTreeNodes();
            });
        }

        self.refresh = function() {
            confirmSaveFile(function() {
                self.fullPath = $location.path().replace(/^\/mock-editor/, '');
                loadTreeNodes();
                toastr.success('文件列表已更新！');
            });
        }

        self.toggleView = function(preview) {
            if (preview) {
                self.itemIdOffset++;
                self.items = xUtil.mockdoc.parse(dataEditor.getValue());
            }
            // Toggle preview
            self.preview = mockView.formEditor = preview;
            $location.path($location.path(), false).search('preview', (self.preview ? 'true' : null)).replace();
            // Save view
            $cookies.putObject('mock_view', mockView);
            if (preview) {
                $rootScope.$broadcast('editor.disable');
            } else {
                $timeout(function() {
                    dataEditor.focus();
                    dataEditor.resize();
                });
            }
        }

        self.toggleFullScreen = function() {
            self.fullScreen = !self.fullScreen;
            $location.path($location.path(), false).search('fullscreen', (self.fullScreen ? 'true' : null)).replace();
        }

        function confirmSaveFile(callback, callable) {
            if (self.status.enabled && self.status.changed) {
                xDialog.confirm('确认信息', '文件数据未保存，您确定要离开吗？').then(function(yes) {
                    if (yes) {
                        self.status.changed = false;
                        callback();
                    } else {
                        dataEditor.focus();
                    }
                });
                return false;
            }
            if (callable !== false) {
                callback();
            }
            return true;
        }

        function loadTreeNodes() {
            self.textMock = '';
            self.status.changed = false;
            self.status.enabled = false;

            self.node.parent = '';
            self.node.name = '';
            self.node.root = false;
            self.node.leaf = false;

            $('#tree-filter').val('');
            $.fn.zTree.destroy('mock-tree');
            $http.get('/mock/data/tree?module=' + self.module).then(function(response) {
                if (response.data === false) {
                    xDialog.alert('提示信息', '数据模块【' + self.module + '】不存在！');
                } else if (response.status == 200) {
                    var children = response.data['children'];
                    if (mockView.flatTree) {
                        var nodes = toFlatTreeNodes('', children);
                        children = _.sortBy(nodes, function(node) {
                            return node.name/*.replace(/\{|\}/g, '')*/.replace(/\./g, '/');
                        })
                    }
                    var treeNode = { name: 'MOCK', open: true, root: true, leaf: false, children: children };
                    zTreeObj = $.fn.zTree.init($('#mock-tree'), setting, treeNode);
                    countRootApis();
                    fuzzySearch('mock-tree', '#tree-filter', true, true, countRootApis);
                    if (!_.isEmpty(self.fullPath)) {
                        var nodes = zTreeObj.getNodesByFilter(function(node) {
                            node.parent = getParentPath(node);
                            if (mockView.flatTree) {
                                return getNodePath(node).indexOf(self.fullPath) == 0;
                            } else {
                                return getNodePath(node) == self.fullPath;
                            }
                        });
                        if (_.isEmpty(nodes)) {
                            var path = self.fullPath.replace(new RegExp('^/' + self.module), '');
                            if (path) {
                                xDialog.alert('提示信息', '文件路径【' + path + '】不存在！');
                            }
                        } else {
                            zTreeObj.selectNode(nodes[0]);
                            zTreeObj.expandNode(nodes[0], true, false, false);
                            $('#' + nodes[0].tId + '>a').trigger('click');
                        }
                        self.fullPath = '';
                    }
                }
            });
        }

        function toFlatTreeNodes(parent, nodes) {
            var flatNodes = [];
            _.each(nodes, function(node) {
                node.parent = parent;
                var children = node.children;
                if (node.leaf || _.isEmpty(children)) {
                    var _node = {};
                    _.each(node, function(value, key) {
                        if (key != 'children') {
                            if (key == 'name') {
                                _node[key] = node.parent + '/' + node[key];
                                _node.parent = '';
                            } else if (key != 'parent') {
                                _node[key] = node[key];
                                if (key == 'leaf' && !_node[key]) {
                                    _node.isParent = true;
                                }
                            }
                        }
                    });
                    flatNodes.push(_node);
                } else if (!_.isEmpty(children)) {
                    var _nodes = toFlatTreeNodes(parent + '/' + node.name, children);
                    _.each(_nodes, function(_node) {
                        flatNodes.push(_node);
                    });
                }
            });
            return flatNodes;
        }

        function getParentPath(node) {
            var paths = node.getPath();
            var parent = '';
            for (var i = 1; i < paths.length - 1; i++) {
                parent += '/' + (paths[i].oldname || paths[i].name);
            }
            return parent;
        }

        function getNodePath(node) {
            var path = '/' + self.module + node.parent;
            path = xUtil.path.join([path, (node.oldname || node.name)]);
            if (!node.leaf) {
                path = (node.root ? '/' + self.module : path) + '/';
            }
            return path;
        }

        function getMockPath() {
            return (self.node.url == '' ?
                        self.node.url : xUtil.regex.urlMatch('', self.node.url)
                    ).replace(/@index$/, '');
        }

        function getMockMethod(method) {
            method = xUtil.regex.methodMatch('', self.node.url) || method;
            return method == '' ? 'POST' : method;
        }

        function checkMockData(data) {
            var errors = [];
            // @标题
            if (/^\s*$/.test(xUtil.regex.keyMatch(data, '标题'))) {
                errors.push('【标题】不可以为空！');
            }
            // @URL
            var customizedUrl = xUtil.regex.urlMatch(data, '');
            if (/^\/[\[\{"\w-:,\}\]\.\/\?%&=]+$/g.test(customizedUrl)) {
                var url = getMockPath();
                if (customizedUrl != url) {
                    customizedUrl = xUtil.regex.subMatch(customizedUrl, /^(\/[^\?]+)\??/g);
                    if (customizedUrl != url) {
                        var rplaceholder = /\{[0-9A-Za-z_-]+\}/g;
                        url = url.replace(rplaceholder, '{placeholder}');
                        customizedUrl = customizedUrl.replace(rplaceholder, '{placeholder}');
                        if (customizedUrl != url) {
                            errors.push('【URL】与MOCK路径不匹配！');
                        }
                    }
                }
            } else {
                errors.push('【URL】格式不正确！');
            }
            // @Method
            var method = xUtil.regex.methodMatch(data, '');
            if (/^(get|post|put|patch|delete)$/i.test(method)) {
                if (method != getMockMethod(method)) {
                    errors.push('【Method】与MOCK请求不匹配！');
                }
            } else {
                errors.push('【Method】请求方式不支持！');
            }
            // @Headers
            try {
                JSON5.parse(xUtil.regex.keyMatch(data, 'Headers'));
            } catch (e) {
                errors.push('【Headers】格式不正确！');
            }
            // @Query
            if (xUtil.regex.hasKey(data, 'Query')) {
                try {
                    JSON5.parse(xUtil.regex.keyMatch(data, 'Query'));
                } catch (e) {
                    errors.push('【Query】格式不正确！');
                }
            }
            // @入参
            try {
                if (self.extname == '.json') {
                    JSON5.parse(xUtil.regex.keyMatch(data, '入参'));
                } else if (self.extname == '.xml') {
                    $.parseXML(xUtil.regex.keyMatch(data, '入参'));
                }
            } catch (e) {
                errors.push('【入参】格式不正确！');
            }

            toastr.clear();
            setTimeout(function() {
                for (var i = 0; i < errors.length; i++) {
                    (function (index) {
                        setTimeout(function() {
                            toastr.error(errors[index]);
                        }, index * 200);
                    })(i);
                }
            }, 500);

            return errors.length > 0;
        }

        function countRootApis() {
            var rootNode = zTreeObj.getNodeByTId('mock-tree_1');
            var nodes = zTreeObj.getNodesByFilter(function(node) {
                return node.leaf && !node.isHidden;
            }, false, rootNode);
            rootNode.name = 'MOCK（' + nodes.length + '）';
            zTreeObj.updateNode(rootNode);
            zTreeObj.showNode(rootNode);
        }

        self.save = function() {
            var dataValue = dataEditor.getValue();
            if (self.preview) {
                dataValue = xUtil.mockdoc.stringify(self.items, self.extname);
            }
            if (checkMockData(dataValue)) {
                return;
            }
            xDialog.confirm('确认信息', '您确定要保存文件【' + self.node.name + '】的数据吗？').then(function(yes) {
                if (yes) {
                    $http.post('/mock/data/save', {
                        module: self.module,
                        type: 'file',
                        folder: self.node.parent,
                        file: self.node.name,
                        data: dataValue,
                        modified: self.node.modified
                    }).then(function(response) {
                        setTimeout(function() {
                            if (response.data == 'required') {
                                xDialog.alert('提示信息', '数据模块【' + self.module + '】不存在！');
                            } else if (response.data == 'expired') {
                                xDialog.alert('提示信息', '文件数据已被更新，请刷新后再保存！');
                            } else if (response.data) {
                                // Reload committed data
                                $http({
                                    method: 'POST',
                                    url: getNodePath(self.node) + '?origin=true',
                                    transformResponse: function(_response) {
                                        return _response;
                                    }
                                }).then(function(_response) {
                                    if (_response.status == 200) {
                                        self.node.modified = _response.headers('x-modified');
                                        if (self.textMock != _response.data) {
                                            self.textMock = _response.data;
                                            dataEditor.setValue(_response.data, -1);
                                            if (self.preview) {
                                                initItems = true;
                                                self.itemIdOffset++;
                                                self.items = xUtil.mockdoc.parse(_response.data);
                                            }
                                        }
                                    }
                                });
                                //self.node.modified = response.data;
                                toastr.success('文件数据已保存！');
                                self.status.changed = false;
                                self.textMock = dataEditor.getValue();
                            }
                        }, 500)
                    });
                }
            });
        }

        self.upload = function(type) {
            confirmSaveFile(function() {
                xDialog.open().importMock({
                    type: type, module: self.module, node: self.node
                }).then(function(path) {
                    setTimeout(function() {
                        path = '/' + path + (type == 'zip' ? '/' : '');
                        if (selectedNode) {
                            var nodePath = getNodePath(selectedNode);
                            if (path == nodePath) {
                                $('#' + selectedNode.tId + '>a').trigger('click');
                            } else {
                                self.fullPath = path;
                                loadTreeNodes();
                            }
                        } else {
                            self.fullPath = path;
                            loadTreeNodes();
                        }
                    }, 500);
                });
            });
        }

        self.saveAs = function() {
            var filename = (selectedNode.oldname || selectedNode.name);
            var slashIndex = filename.lastIndexOf('/');
            if (slashIndex != -1) {
                filename = filename.substring(slashIndex + 1);
            }
            // Save file
            var content = '';
            if (self.preview) {
                content = xUtil.mockdoc.stringify(self.items, self.extname);
            } else {
                content = dataEditor.getValue();
            }
            xUtil.file.save(filename, content);
        }

        self.saveAsZip = function() {
            xUtil.file.download('/mock/data/export?path=' + self.node.url);
        }

        self.callback = function(height, width, refresh) {
            // Size of form editor
            $('.x-form-editor .panel-body').css('height', (height - 42) + 'px');
            // Toolbar
            if (refresh) {
                $('.navbar-form').append($compile($('#opt-toolbar').html())($scope));
            }
        }

        self.handle = function(name, value) {
            switch (name) {
                case 'font':
                    self.fontSize = value;
                    dataEditor.setFontSize(value);
                    break;
                case 'edit':
                    xDialog.open().editMockDoc({ type: value, extname: self.extname, editor: dataEditor });
                    break;
                case 'undo':
                case 'redo':
                    dataEditor[name]();
                    break;
                case 'format':
                    if (self.preview) {
                        $rootScope.$broadcast('editor.action', 'format');
                    } else {
                        dataEditor.execCommand('format');
                    }
                    toastr.success('文件数据已格式化！');
                    break;
                case 'wrap':
                    self.toggleWrap = (dataEditor.getOption('wrap') == 'off' ? true : false);
                    dataEditor.setOption('wrap', self.toggleWrap);
                    break;
                case 'fold':
                    if (self.preview) {
                        $rootScope.$broadcast('editor.action', 'fold');
                    } else {
                        dataEditor.getSession().foldAll(1);
                    }
                    break;
                case 'unfold':
                    if (self.preview) {
                        $rootScope.$broadcast('editor.action', 'unfold');
                    } else {
                        dataEditor.getSession().unfold();
                    }
                    break;
                default:
                    break;
            }
            dataEditor.resize();
        }

        // Show searchbox
        self.showSearchbox = function() {
            xUtil.searchbox.show(dataEditor);
        }

        // Show shortcut key
        self.showShortcutKey = function() {
            xDialog.open().showShortcutKey();
        }

        // Goto top of editor
        self.gotoTop = function() {
            dataEditor.scrollToLine(0);
        }

        // Show mock test
        self.showMockTest = function() {
            xDialog.open().testMock({
                path: self.node.url,
                extname: self.extname,
                data: dataEditor.getValue()
            });
        }
    });
});
